package server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;

import scheduler.ILinkRepository;
import structures.InfoPacket;
import structures.TaskType;
import crawlerGUI.CrawlerMainWindow;

//klasa koordynująca działania wątków odpowiedzialnych za poszczególnych klientów 
class WorkManager {
	private static boolean isShuttingDown = false;
	private static boolean _isStopping = false;
	private static InfoPacket packet = null;
	public static Object lock=new Object();

	static boolean isShuttingDown() {
		return isShuttingDown;
	}
	
	static boolean isStopping() {
		return _isStopping;
	}


	static void setShuttingDown() {
		WorkManager.isShuttingDown = true;
	}

	static void unsetShuttingDown() {
		WorkManager.isShuttingDown = false;
	}

	static void setStopping() {
		WorkManager._isStopping = true;
	}

	static void unsetStopping() {
		WorkManager._isStopping = false;
	}

	static synchronized void setDeafultPacket(InfoPacket packetinfo) {
		packet = packetinfo;
	}
	
	static synchronized InfoPacket getDefaultPacket(){return packet;}

}

// wątek do wysyłania nowych wątków
class ConnectionWriterThread implements Runnable {

	// socket konkretnego klienta
	private Socket sock;
	// strumień do wysyłania do klientów
	private ObjectOutputStream out;
	// domyślny pakiet z zdaniami do wykonania
	private InfoPacket _defaultPacket;
	private ILinkRepository repository;
	Thread shutDownThread = null;

	// zmienienie tasków poprzez przekazanie InfoPacket
	public void updateDefaultPacket(InfoPacket pck) {
		this._defaultPacket = pck;
	}

	// konstruktor
	ConnectionWriterThread(Socket sock, ILinkRepository linkRep ) {
		this.sock = sock;
		this._defaultPacket = null;
		this.repository=linkRep;
	}

	public void run() {

		try {
			out = new ObjectOutputStream(sock.getOutputStream());
			String URL;
			while (!sock.isClosed() && (URL = this.repository.getLink()) != null) {
				
				//jeśli manager każe czekać to zaprzestaje wysyłania nowych zadań
				synchronized(WorkManager.lock){
					while(WorkManager.isStopping())
						try {
							System.out.println("Waiting for more work, clients could still send more packets");
							WorkManager.lock.wait();
						} catch (InterruptedException e) {
						}
				}
				//pobiera aktualne zadania
				this._defaultPacket=WorkManager.getDefaultPacket();
				
				InfoPacket packet = new InfoPacket(URL);
				packet._taskArguments = this._defaultPacket._taskArguments;
				
				if (WorkManager.isShuttingDown()) {
					
					System.out.println("ConnectionWriterThread shutting down");
					out.close();
					sock.close();
					if (sock.isClosed())
						System.out.println("Socket closed");
					return;
					
				} else {
					packet._taskList = new LinkedList<TaskType>();
					packet._taskList.addAll(this._defaultPacket._taskList);
				}
				
				out.writeObject(packet);

			}

		} catch (IOException e) {
			System.out.println("Socket closed.");
		}
	}

}

// ***********************************************************************************

class ConnectionReaderThread implements Runnable {

	private Socket sock;
	private ILinkRepository linkRep;
	private CrawlerMainWindow _window;
	private ObjectInputStream in;

	ConnectionReaderThread(Socket sock, ILinkRepository linkRep,CrawlerMainWindow window) {
		this.sock = sock;
		this.linkRep = linkRep;
		this._window = window;
	}

	public void run() {
		try {
			in = new ObjectInputStream(sock.getInputStream());
			InfoPacket pck = null;

			while (!sock.isClosed()
					&& (pck = (InfoPacket) in.readObject()) != null) {
				
				if (WorkManager.isShuttingDown()) {
					System.out.println("ConnectionReaderThread shutting down");
					in.close();
					sock.close();
					return;
				}
				linkRep.saveLinks(pck.getUrlList());
				this._window.updateURLs(pck.getUrlList());

				// przeszukuje listę zadań i w zależności od TaskType wykonuje
				// odpowiednią czynność
				for (TaskType t : pck.getTaskList()) {
					if (t == TaskType.keyword
							&& (Boolean) pck.getTaskArgument(TaskType.keyword)) {
						// System.out.println("Znalazłem ----------\n +");
						this._window.updatePhrasesFound(pck.getPage());
					}
				}
			}
		} catch (IOException e) {
			System.out.println("Socket closed.");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

}

// ***********************************************************************************

public class CrawlerServer implements Runnable {
	private ServerSocket srvSock;
	private ILinkRepository linkRep;
	private CrawlerMainWindow _window;
	private ConnectionWriterThread _writer;
	private ConnectionReaderThread _reader;
	private int port;

	public CrawlerServer(int port, 	ILinkRepository linkRep, CrawlerMainWindow window,
			InfoPacket defaultPacket) {
		this.port = port;
		this.linkRep = linkRep;
		this._window = window;
		WorkManager.setDeafultPacket(defaultPacket);
		try {
			srvSock = new ServerSocket(this.port);
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public void run() {
		System.out.println("CrawlerServer rozpoczyna działanie");
		Socket s;

		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					srvSock.close();
				} catch (IOException e) {
					e.printStackTrace();
				}

			}
		}));

		while (true) {
			try {
				while ((s = srvSock.accept()) != null) {
					this._window.addClient(s.getRemoteSocketAddress()
							.toString());
					System.out.println("CrawlerServer zaakceptował połączenie");
					this._writer = new ConnectionWriterThread(s, linkRep);
					new Thread(this._writer).start();
					this._reader = new ConnectionReaderThread(s, linkRep,_window);
					new Thread(this._reader).start();
				}

			} catch (IOException e) {
				
			}
		}
	}

	// do zmiany zadania
	public void updateDefaultTaskPacket(InfoPacket pck) {
		WorkManager.setDeafultPacket(pck);
		this.start();

	}

	//zatrzymanie serwera
	public synchronized void stop() {
		WorkManager.setStopping();
		
	}
	
	//rozpoczęcie działania serwera
	public synchronized void start() {
		this.linkRep.resetRegistry();
		WorkManager.unsetStopping();
		synchronized(WorkManager.lock){
		WorkManager.lock.notifyAll();}
		
	}
}
